﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace ZeroBug.EasyFlow.Core
{
    public class VariableManager
    {
        public SerializableDictionary<string, Variable> GlobalVariables { get; set; }
        /// <summary>
        /// 配方变量，保存在配方里
        /// </summary>
        [XmlIgnore]
        public SerializableDictionary<string, Variable> RecipeVariables { get; set; }
        /// <summary>
        /// 局部变量，无需保存
        /// </summary>
        [XmlIgnore]
        public SerializableDictionary<string, Variable> LocalVariables { get; set; }
        /// <summary>
        /// 资源变量，无需保存
        /// </summary>
        [XmlIgnore]
        public SerializableDictionary<string, Variable> ResourceVariables { get; set; }
        public VariableManager()
        {
            this.GlobalVariables = new SerializableDictionary<string, Variable>();
            this.LocalVariables = new SerializableDictionary<string, Variable>();
            this.ResourceVariables = new SerializableDictionary<string, Variable>();
            this.RecipeVariables = new SerializableDictionary<string, Variable>();
        }
        public void AddVariablesFromResource<T>(T resourceInstance) where T : IResourceVariable
        {
            typeof(T).GetProperties()
                     .Where(pi => pi.GetCustomAttribute(typeof(NumericVariableAttribute)) != null)
                     .ToList()
                     .ForEach(pi => AddVariable(CreateMachineVariable<T>(pi, resourceInstance, VariableType.Numeric, typeof(T).Name, resourceInstance.Description), VariableFrom.Resource));

            typeof(T).GetProperties()
                     .Where(pi => pi.GetCustomAttribute(typeof(BooleanVariableAttribute)) != null)
                     .ToList()
                     .ForEach(pi => AddVariable(CreateMachineVariable<T>(pi, resourceInstance, VariableType.Boolean, typeof(T).Name, resourceInstance.Description), VariableFrom.Resource));

            typeof(T).GetMethods()
                     .Where(mi => mi.GetCustomAttribute(typeof(BooleanVariableAttribute)) != null)
                     .ToList()
                     .ForEach(mi => AddVariable(CreateMachineVariable<T>(mi, resourceInstance, VariableType.Boolean, typeof(T).Name, resourceInstance.Description), VariableFrom.Resource));

            typeof(T).GetProperties()
                     .Where(pi => pi.GetCustomAttribute(typeof(TextVariableAttribute)) != null)
                     .ToList()
                     .ForEach(pi => AddVariable(CreateMachineVariable<T>(pi, resourceInstance, VariableType.Text, typeof(T).Name, resourceInstance.Description), VariableFrom.Resource));
        }
        public Variable CreateMachineVariable<T>(PropertyInfo pi, T resource, VariableType varType, string group, string belongTo) where T : IResourceVariable
        {
            switch(varType)
            {
                case VariableType.Numeric:
                    return new Variable()
                    {
                        Name = $"{belongTo}.{pi.Name}",
                        VariableType = varType,
                        GetCurrentValues = () =>
                        {
                            if (pi.PropertyType == typeof(List<double>))
                            {
                                return ((List<double>)pi.GetValue(resource)).Select(d => d.ToString()).ToArray();
                            }
                            else if (pi.PropertyType == typeof(double[]))
                            {
                                return ((double[])pi.GetValue(resource)).Select(d => d.ToString()).ToArray();
                            }
                            else
                            {
                                return new string[1] { pi.GetValue(resource).ToString() };
                            }
                        },
                        Group = group,
                    };
                case VariableType.Boolean:
                    return new Variable()
                    {
                        Name = $"{belongTo}.{pi.Name}",
                        VariableType = varType,
                        GetCurrentValues = () =>
                        {
                            if (pi.PropertyType == typeof(List<bool>))
                            {
                                return ((List<bool>)pi.GetValue(resource)).Select(d => d.ToString()).ToArray();
                            }
                            else if (pi.PropertyType == typeof(string[]))
                            {
                                return ((bool[])pi.GetValue(resource)).Select(d => d.ToString()).ToArray();
                            }
                            else
                            {
                                return new string[1] { pi.GetValue(resource).ToString() };
                            }
                        },
                        Group = group,
                    };
                case VariableType.Text:
                    return new Variable()
                    {
                        Name = $"{belongTo}.{pi.Name}",
                        VariableType = varType,
                        GetCurrentValues = () =>
                        {
                            if (pi.PropertyType == typeof(List<string>))
                            {
                                return ((List<string>)pi.GetValue(resource)).ToArray();
                            }
                            else if (pi.PropertyType == typeof(string[]))
                            {
                                return (string[])pi.GetValue(resource);
                            }
                            else
                            {
                                return new string[1] { pi.GetValue(resource).ToString() };
                            }
                        },
                        Group = group,
                    };
            }
            return null;
        }
        public Variable CreateMachineVariable<T>(MethodInfo mi, T resource, VariableType varType, string group, string belongTo) where T : IResourceVariable
        {
            return new Variable()
            {
                Name = $"{belongTo}.{mi.Name}",
                VariableType = varType,
                GetCurrentValues = () =>
                {
                    return new string[] { mi.Invoke(resource, null).ToString() };
                },
                Group = group,
            };
        }
        public void AddVariable(Variable variable, VariableFrom varFrom)
        {
            switch(varFrom)
            {
                case VariableFrom.Global:
                    if (GlobalVariables.ContainsKey(variable.Name))
                        GlobalVariables[variable.Name] = variable;
                    else
                        GlobalVariables.Add(variable.Name, variable);
                    break;
                case VariableFrom.Local:
                    if (LocalVariables.ContainsKey(variable.Name))
                        LocalVariables[variable.Name] = variable;
                    else
                        LocalVariables.Add(variable.Name, variable);
                    break;
                case VariableFrom.Resource:
                    if (ResourceVariables.ContainsKey(variable.Name))
                        ResourceVariables[variable.Name] = variable;
                    else
                        ResourceVariables.Add(variable.Name, variable);
                    break;
                case VariableFrom.Recipe:
                    if (RecipeVariables.ContainsKey(variable.Name))
                        RecipeVariables[variable.Name] = variable;
                    else
                        RecipeVariables.Add(variable.Name, variable);
                    break;
            }
            variable.SetVariableManager(this);
        }
        public Variable GetVariable(string name)
        {
            if(GlobalVariables.ContainsKey(name))
                return GlobalVariables[name];
            if (LocalVariables.ContainsKey(name))
                return LocalVariables[name];
            if (ResourceVariables.ContainsKey(name))
                return ResourceVariables[name];
            if (RecipeVariables.ContainsKey(name))
                return RecipeVariables[name];
            return null;
        }
        public void Save(string solutionName)
        {
            XmlSerializerHelper<VariableManager>.SerializeToFile(this, PathHelper.GetConfigPath("Solutions" + $"\\{solutionName}") + $"Variable.xml");
        }
        public void Load(string solutionName)
        {
            VariableManager mv = XmlSerializerHelper<VariableManager>.DeserializeFromFile(PathHelper.GetConfigPath("Solutions" + $"\\{solutionName}") + $"Variable.xml");
            foreach (var item in mv.GlobalVariables)
            {
                AddVariable(item.Value, VariableFrom.Global);
            }
        }
    }
}
